package main

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"encoding/base64"
	"gRPCSecurity/pb"
	"io/ioutil"
	"log"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"google.golang.org/protobuf/types/known/wrapperspb"
)

//启用单向安全连接
//在单向连接中，只有客户端会校验服务器端，以确保它接受的数据来自预期的服务器。在建立连接时，服务器端会与客户端共享其公开证书，客户端则会校验接受到的证书。
//这是通过证书授权中心(CA)完成的，也就是CA签署的证书。证书校验之后，客户端会发送使用秘钥加密的数据。
/*
var (
	address  = "localhost:50051"
	hostname = "localhost"
	crtFile  = "server.crt"
)

func main() {
	creds, err := credentials.NewClientTLSFromFile(crtFile, hostname)
	if err != nil {
		log.Fatalf("filed to load credentials: %v", err)
	}
	opts := []grpc.DialOption{
		grpc.WithTransportCredentials(creds),
	}
	conn, err := grpc.Dial(address, opts...)
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	defer conn.Close()
	c := pb.NewEchoManagementClient(conn)
	m, err := c.EchoRequest(context.Background(), &wrapperspb.StringValue{Value: "test"})
	if err != nil {
		log.Fatalf("EchoRequest err: %v", err)
	}
	log.Println(m.Value)
}

*/

//启用mTLS保护的连接
//客户端和服务端采用mTLS连接的主要目的是，控制能够连接服务器端的客户端。与单向安全连接不同，这种方式只会讲服务器
//配置为仅接受来自一组范围有限、已验证的客户端的连接。在这种方式中，双方彼此共享公开证书，并校验对方的身份。
/*
var (
	address  = "localhost:50051"
	hostname = "localhost"
	crtFile  = "client.crt"
	keyFile  = "client.key"
	caFile   = "ca.crt"
)

func main() {
	//通过服务器端的证书和密钥直接创建X.509密钥对
	certificate, err := tls.LoadX509KeyPair(crtFile, keyFile)
	if err != nil {
		log.Fatalf("failed to load key pair: %s", err)
	}

	//通过CA创建证书池
	certPool := x509.NewCertPool()
	ca, err := ioutil.ReadFile(caFile)
	if err != nil {
		log.Fatalf("could not read ca certificate: %s", err)
	}

	certPool.AppendCertsFromPEM(ca)
	opts := []grpc.DialOption{
		grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
			ServerName:   hostname,
			Certificates: []tls.Certificate{certificate},
			RootCAs:      certPool,
		})),
	}
	conn, err := grpc.Dial(address, opts...)
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	defer conn.Close()
	c := pb.NewEchoManagementClient(conn)
	m, err := c.EchoRequest(context.Background(), &wrapperspb.StringValue{Value: "test"})
	if err != nil {
		log.Fatalf("EchoRequest err: %v", err)
	}
	log.Println(m.Value)
}

*/

//前面介绍了如何使用TLS实现客户端和服务器端的加密数据交换。下面讨论如何验证调用者的身份
//为了方便对调用者进行验证，gRPC为客户端提供了在每次调用中插入凭证的功能。gRPC服务器端
//能够拦截来自客户端的请求，并检查每一个传入调用的凭证

//使用basic认证
//basic认证是最简单的认证机制。在这种机制中，客户端发送的请求带有Authorization头信息。
//该头信息的值以单词Basic开头，随后是一个空格和base64编码的字符串<用户名>:<密码>
//gRPC不提倡使用用户名和密码来认证，因为其没有有效期，列举这个只是为了阐述原理
var (
	address  = "localhost:50051"
	hostname = "localhost"
	crtFile  = "client.crt"
	keyFile  = "client.key"
	caFile   = "ca.crt"
)

type basicAuth struct {
	username string
	password string
}

func (b basicAuth) GetRequestMetadata(ctx context.Context, in ...string) (map[string]string, error) {
	auth := b.username + ":" + b.password
	enc := base64.StdEncoding.EncodeToString([]byte(auth))
	return map[string]string{"authorization": "Basic " + enc}, nil
}

func (b basicAuth) RequireTransportSecurity() bool {
	return true
}

func main() {
	//通过服务器端的证书和密钥直接创建X.509密钥对
	certificate, err := tls.LoadX509KeyPair(crtFile, keyFile)
	if err != nil {
		log.Fatalf("failed to load key pair: %s", err)
	}

	//通过CA创建证书池
	certPool := x509.NewCertPool()
	ca, err := ioutil.ReadFile(caFile)
	if err != nil {
		log.Fatalf("could not read ca certificate: %s", err)
	}

	certPool.AppendCertsFromPEM(ca)

	auth := basicAuth{
		username: "admin",
		password: "admin",
	}

	opts := []grpc.DialOption{
		grpc.WithPerRPCCredentials(auth),
		grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
			ServerName:   hostname,
			Certificates: []tls.Certificate{certificate},
			RootCAs:      certPool,
		})),
	}
	conn, err := grpc.Dial(address, opts...)
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	defer conn.Close()
	c := pb.NewEchoManagementClient(conn)
	m, err := c.EchoRequest(context.Background(), &wrapperspb.StringValue{Value: "test"})
	if err != nil {
		log.Fatalf("EchoRequest err: %v", err)
	}
	log.Println(m.Value)
}
